#include "compat_event_iocp.h"
#include "compat_event_win32.h"

#if (TARGET_OS == OS_WIN)

void _waitsem_threadProc(void* obj)
{
    iocp_op_t* base = NULL;

    base = (iocp_op_t*)obj;

    if (base != NULL) {
        _cond_signal(&base->waitcond);
    }
}
static HMODULE load_windows_system_library_(const WCHAR* library_name)
{
    WCHAR path[MAX_PATH] = {0x00};
    unsigned n;
    n = GetSystemDirectoryW(path, MAX_PATH);
    if (n == 0 || n + s_wstrlen(library_name) + 2 >= MAX_PATH)
        return 0;
    s_wstrcat(path, MAX_PATH, _T("\\"));
    s_wstrcat(path, MAX_PATH, library_name);
    return LoadLibrary(path);
}

static void load_library(HMODULE hMod)
{
    FreeLibrary(hMod);
}
static void* get_mswsock_extfunc(SOCKET s, const GUID* which_fn)
{
    void* ptr = NULL;
    DWORD bytes = 0;
    WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, (GUID*)which_fn, sizeof(*which_fn), &ptr, sizeof(ptr), &bytes, NULL, NULL);
    return ptr;
}

static int iocp_hook_api(iocp_op_t* base)
{
    // process==>kernel32.dll
    HMODULE hModule = load_windows_system_library_(TEXT("kernel32.dll"));
	rc_error(hModule != NULL, S_ERROR);
	base->iocp.CancelIo = (CancelIoPtr)GetProcAddress(hModule, "CancelIo");
    base->iocp.CancelIoEx = (CancelIoExPtr)GetProcAddress(hModule, "CancelIoEx");
    FreeLibrary(hModule);
	
    return S_SUCCESS;
}

static int sock_hook_api(iocp_op_t* base)
{
    const GUID acceptex = WSAID_ACCEPTEX;
    const GUID connectex = WSAID_CONNECTEX;
    const GUID getacceptexsockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
    const GUID disconnectex = WSAID_DISCONNECTEX;

    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == INVALID_SOCKET)
        return S_ERROR;

    base->fns.AcceptEx = NULL;
    base->fns.ConnectEx = NULL;
    base->fns.GetAcceptExSockaddrs = NULL;
    base->fns.DisconnectEx = NULL;

    base->fns.AcceptEx = get_mswsock_extfunc(s, &acceptex);
    if (base->fns.AcceptEx == NULL)
        goto err;

    base->fns.ConnectEx = get_mswsock_extfunc(s, &connectex);
    if (base->fns.ConnectEx == NULL)
        goto err;

    base->fns.GetAcceptExSockaddrs = get_mswsock_extfunc(s, &getacceptexsockaddrs);

    base->fns.DisconnectEx = get_mswsock_extfunc(s, &disconnectex);

    closesocket(s);
    return S_SUCCESS;
err:
    closesocket(s);
    return S_ERROR;
}

static init_cond_thread(iocp_op_t* base)
{
    int rc = S_ERROR;

    base->waitcond.relative = WAIT_TIME_RELATIVE;

    rc = _cond_init(&base->waitcond);
    if (rc != S_SUCCESS)
        goto err;

    rc = _mutex_init(&base->waitlock);
    if (rc != S_SUCCESS)
        goto err;

    base->waitsem_thread = NULL;

    rc = _thread_create(&base->waitsem_thread, _waitsem_threadProc, base);
    if (rc != S_SUCCESS)
        goto err;

    return S_SUCCESS;

err:
    _mutex_destroy(&base->waitlock);
    _cond_destroy(&base->waitcond);
    if (base->waitsem_thread != NULL)
        _thread_join(&base->waitsem_thread);

    return S_ERROR;
}

int iocp_alloc(evloop_t* loop, int size)
{
    int rc = 0;
    iocp_op_t* base = NULL;
    base = (iocp_op_t*)loop->mm_malloc(loop, 1, sizeof(iocp_op_t));
    rc_error(base != NULL, S_ERROR);

    base->size = size;
    base->hiocp = INVALID_HANDLE_VALUE;
    base->hiocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, base->size);

    if (base->hiocp == INVALID_HANDLE_VALUE)
        goto err;

    rc = sock_hook_api(base);
    if (rc != S_SUCCESS)
        goto err;

    rc = iocp_hook_api(base);
    if (rc != S_SUCCESS)
        goto err;

    rc = init_cond_thread(base);
    if (rc != S_SUCCESS)
        goto err;

	evloop_queue_init(&base->queue);
	
    return evloop_bind_evbase(loop, base);

err:
    if (base->hiocp != INVALID_HANDLE_VALUE)
        CloseHandle(base->hiocp);

    heap_free(base);

    return S_ERROR;
}
int iocp_dealloc(evloop_t* loop)
{
    return S_SUCCESS;
}
int iocp_handler(evloop_t* loop, ev_op ev)
{
    int rc = S_ERROR;
    rc_error(loop != NULL, S_ERROR);
    iocp_op_t* base = (iocp_op_t*)evloop_evbase(loop);
    rc_error(base != NULL, S_ERROR);
    base->ev = ev;
    return S_SUCCESS;
}
int iocp_alloc_evoper(evloop_t* loop, evoper_t* oper)
{
    iocp_op_t* base = (iocp_op_t*)evloop_evbase(loop);
    rc_error(base != NULL, S_ERROR);
    HANDLE handle = CreateIoCompletionPort(oper->fd, base->hiocp, oper->t, 0);
    rc_error(handle != NULL, S_ERROR);

    evoper_op_init(oper);

    lpevoper_ov obj = (lpevoper_ov)loop->mm_malloc(loop, 1, sizeof(evoper_ov));
    rc_error(obj != NULL, S_ERROR);

    evoper_bind_private(oper, obj);

    obj->ptr = oper;
    obj->r = ev_off;
    obj->w = ev_off;

    s_memset(&obj->overlapped, 0, sizeof(OVERLAPPED));

    return S_SUCCESS;
}

int iocp_dealloc_evoper(evloop_t* loop, evoper_t* oper)
{
    lpevoper_ov obj = (lpevoper_ov)evoper_private(oper);
    if (HasOverlappedIoCompleted(&obj->overlapped)) {
    } else {
    }

    return S_SUCCESS;
}
int iocp_enable_read(evloop_t* loop, evoper_t* oper, short event, ev_time_t tv)
{
    int rc = S_ERROR;

    rc_error(oper != NULL, S_ERROR);
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);

    if (oper->t == ev_sock) {
        rc = win32_sock_read_op(oper);
    }
    return rc;
}
int iocp_enable_write(evloop_t* loop, evoper_t* oper, short event, ev_time_t tv)
{
    int rc = S_ERROR;
    if (oper->t == ev_sock) {
        rc = win32_sock_write_op(oper);
    }
    return rc;
}
int iocp_disable_read(evloop_t* loop, evoper_t* oper, short event)
{
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    return S_SUCCESS;
}
int iocp_disable_write(evloop_t* loop, evoper_t* oper, short event)
{
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    return S_SUCCESS;
}
int iocp_cancel(evloop_t* loop, evoper_t* oper)
{
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    return S_SUCCESS;
}
int iocp_open(evloop_t* loop, evoper_t* oper, const evaddr_t* addr)
{
    int rc = S_ERROR;
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    return rc;
}
int iocp_poll(evloop_t* loop, evoper_t* oper)
{
    int rc = S_ERROR;
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);
    rc_error(oper->t == ev_sockpoll, S_ERROR);
    wsasock* sk = NULL;
    rc = wsasock_create(loop, base, oper, &sk);
    rc_error(rc == S_SUCCESS, S_ERROR);

    return win32_sock_accept(oper, sk);
}
int icop_add_connect(evloop_t* loop, evoper_t* oper, const evaddr_t* addr, ev_time_t tv)
{
    rc_error(oper->t == ev_sock, S_ERROR);
    return win32_sock_connect(oper, addr, tv);
}
int icop_del_connect(evloop_t* loop, evoper_t* oper)
{
    return S_SUCCESS;
}
static int sockpoll_handle(evloop_t* loop, iocp_op_t* base, lpevoper_ov oper_ov, DWORD err, DWORD bytes)
{
    lpwsasock sock = NULL;
    int rc = 0;
    rc_error(oper_ov != NULL, S_ERROR);
    sock = oper_ov->ptr;
    rc_error(sock->oper != NULL, S_ERROR);
    rc_error(base->ev != NULL, S_ERROR);
    win32_sock_accpet_ctx(sock->accept_oper, sock->oper);

    lpevoper_ov obj = (lpevoper_ov)evoper_private(sock->oper);
    evoper_ov_bind_ptr(obj, sock->oper);

    return complete_sock_accept(loop, S_SUCCESS, err, sock->accept_oper, sock->oper, base->ev);
}
static int sock_handle(evloop_t* loop, iocp_op_t* base, lpevoper_ov oper_ov, DWORD err, DWORD bytes)
{
    evoper_t* oper = NULL;
    int rc = 0;
    rc_error(oper_ov != NULL, S_ERROR);
    oper = oper_ov->ptr;
    rc_error(oper != NULL, S_ERROR);

    if (oper->conn == op_ev_connecting) {
        int conn_error = S_SUCCESS;

        // ERROR_INVALID_NETNAME
        if (err == ERROR_CONNECTION_REFUSED || err == ERROR_NETWORK_UNREACHABLE || err == ERROR_HOST_UNREACHABLE || err == ERROR_SEM_TIMEOUT) {
            conn_error = S_ERROR;
        } else {
            // https://learn.microsoft.com/zh-cn/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex
            win32_sock_connect_ctx(oper);
        }
        complete_sock_connect(loop, conn_error, err, oper, base->ev);
        return S_SUCCESS;
    }

    if (bytes == 0) {
        // remote close sock
        complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
        return S_SUCCESS;
    }

    if (oper_ov->r == ev_on && bytes) {

        evoper_set_rbyte(oper, bytes);
        base->ev(0, err, oper, NULL, op_ev_read);
        oper_ov->r = ev_off;
        if (evoper_reading(oper) == S_SUCCESS)
            iocp_enable_read(loop, oper, op_ev_level, NULL);
    }

    if (oper_ov->w == ev_on && bytes) {

        evoper_set_wbyte(oper, bytes);
        base->ev(0, err, oper, NULL, op_ev_write);
        oper_ov->w = ev_off;
        if (evoper_writeing(oper) == S_SUCCESS)
            iocp_enable_write(loop, oper, op_ev_level, NULL);

    }

    return S_SUCCESS;
}
static int sock_iocp_err_handle(evloop_t* loop, iocp_op_t* base, evoper_ov* oper_ov, ULONG_PTR key, DWORD err)
{
    evoper_t* oper = NULL;
    int rc = 0;

    if (key == ev_sockpoll) {
        lpwsasock sock = NULL;
        int rc = 0;
        rc_error(oper_ov != NULL, S_ERROR);
        sock = oper_ov->ptr;
        rc_error(sock->oper != NULL, S_ERROR);
        oper = sock->accept_oper;
    } else if (key == ev_sock) {
        rc_error(oper_ov != NULL, S_ERROR);
        oper = oper_ov->ptr;
        rc_error(oper != NULL, S_ERROR);
    }

    if (err == ERROR_NETNAME_DELETED) {
        complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
        return S_SUCCESS;
    }

    if (err == ERROR_NETNAME_DELETED) {
        // Operation cancelled.
    } else if (err == ERROR_PORT_UNREACHABLE) {
        // Connection refused
        complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
    } else if (err == WSAEMSGSIZE) {
        // Changed the Windows HANDLE
        // backend to treat ERROR_MORE_DATA
        // as a non-fatal error when returned by GetOverlappedResult
        // for a synchronous read.
    } else if (err == ERROR_OPERATION_ABORTED) {
        // cancel;
        complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
    } else {
        complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
    }

    return S_SUCCESS;
}
static int sock_iocp_handle(evloop_t* loop, iocp_op_t* base, evoper_ov* oper_ov, ULONG_PTR key, DWORD err, DWORD bytes)
{
    rc_error(oper_ov != NULL, S_ERROR);

    if (key == ev_sockpoll) {
        sockpoll_handle(loop, base, oper_ov, err, bytes);
    } else if (key == ev_sock) {
        sock_handle(loop, base, oper_ov, err, bytes);
    }
    return S_SUCCESS;
}
int iocp_cmd_poll(iocp_op_t* base, evloop_t* loop, crt_msec timer)
{
    DWORD timeout = 0;
    if (timer == 0)
        timeout = INFINITE;
    else
        timeout = timer;

    BOOL SUCCESS = 0;
    DWORD bytes = 0;
    ULONG_PTR key = 0;

    // Wait for a completion notification.
    evoper_ov* ovlp = NULL;

    SUCCESS = GetQueuedCompletionStatus(base->hiocp, &bytes, &key, (OVERLAPPED**)&ovlp, timer);

    crterr err = crt_geterror();

    if (err) {
        if (ovlp == NULL) {
            if (err != WAIT_TIMEOUT) {
                return NGX_ERROR;
            }
            return S_SUCCESS;
        }
    }

    if (ovlp == NULL)
        return S_AGAIN;

    if (HasOverlappedIoCompleted(&ovlp->overlapped) == 0)
        return S_SUCCESS;

    if (SUCCESS == TRUE) {
        if (key == ev_sock || key == ev_sockpoll) {
            if (err == ERROR_NETNAME_DELETED) {
                sock_iocp_err_handle(loop, base, ovlp, key, err);
            } else {
                sock_iocp_handle(loop, base, ovlp, key, err, bytes);
            }
        }
    } else {
        if (key == ev_sock || key == ev_sockpoll) {
            sock_iocp_err_handle(loop, base, ovlp, key, err);
        }
    }

    return S_SUCCESS;
}

static int iocp_cmd_exit(iocp_op_t* base, evloop_t* loop)
{
    int ret;
    ret = PostQueuedCompletionStatus(base->hiocp, 0, iocp_exit, NULL);
    return ret > 0 ? S_SUCCESS : S_ERROR;
}
int iocp_dispatch(evloop_t* loop, short event, crt_msec time)
{
    int rc = S_ERROR;

    iocp_op_t* base = (iocp_op_t*)evloop_evbase(loop);

    switch (event) {
    case op_cmd_poll:
        rc = iocp_cmd_poll(base, loop, time);
        break;
    case op_cmd_exit:
        rc = iocp_cmd_exit(base, loop);
        break;
    default:
        break;
    }
    return rc;
}

int iocp_evop_t(evsysop_t* op)
{
    op->op_alloc = iocp_alloc;
    op->op_dealloc = iocp_dealloc;
    op->op_handler = iocp_handler;

    op->op_alloc_evoper = iocp_alloc_evoper;
    op->op_dealloc_evoper = iocp_dealloc_evoper;

    op->op_enable_read = iocp_enable_read;
    op->op_enable_write = iocp_enable_write;
    op->op_disable_read = iocp_disable_read;
    op->op_disable_write = iocp_disable_write;

    op->op_cancel = iocp_cancel;
    op->op_open = iocp_open;
    op->op_poll = iocp_poll;
    op->op_add_connect = icop_add_connect;
    op->op_del_connect = icop_del_connect;

    op->op_dispatch = iocp_dispatch;

    return S_SUCCESS;
}
int evoper_ov_bind_ptr(evoper_ov* ov, void* ptr)
{
    rc_error(ov != NULL, S_ERROR);
    ov->ptr = ptr;
    return S_SUCCESS;
}
int wsasock_create(evloop_t* loop, iocp_op_t* base, evoper_t* oper_s, wsasock** sock)
{
    int rc = S_ERROR;
    lpwsasock sk = (lpwsasock)heap_calloc(1, sizeof(wsasock));
    rc_error(sk != NULL, S_ERROR);

    evoper_t* oper = (evoper_t*)loop->mm_malloc(loop, 1, sizeof(evoper_t));
    rc_error(oper != NULL, S_ERROR);

    oper->sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED);
    set_blocking(oper->sock, 1);

    evoper_bind_type(oper, ev_sock);

    rc = evloop_alloc_evoper(loop, oper);
    rc_error(rc == S_SUCCESS, S_ERROR);

    sk->oper = oper;
    sk->accept_oper = oper_s;

    /*
        The current "oper" private stores the evoper_ov pointer
        However, currently it is a server OV operation
        so changing the private pointer is necessary
    */
    lpevoper_ov obj = (lpevoper_ov)evoper_private(oper);
    evoper_ov_bind_ptr(obj, sk);

    *sock = sk;

    return S_SUCCESS;
}
#endif
